
#include "emixer_alsa.h"
#include <errno.h>

// private stuff

static snd_mixer_t* g_mixer_handle = NULL;
static char         g_card_name[64];
static char         g_device_name[64];
static int          g_mixer_refresh = 1; // so we always "refresh" at least once

// hold the mixer level data

typedef struct
{
  snd_mixer_selem_id_t* selem_id;
  snd_mixer_elem_t*   elem;
}
mixer_data_t;

static mixer_data_t* g_mixer_data = NULL;
static int           g_mixer_n_data = 0;

// mixer event callback function
static int
emixer_mixer_cb ( snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem )
{
  //printf ( "mixer cb!\n" );
  g_mixer_refresh = 1;
  return 0;
}

// error out if it all goes badly
void
emixer_abort ( ErrType error, const char * err_string, int xerrno )
{
  ( void ) error;
  printf ( "**FATAL** : %s\n", err_string );
  exit ( xerrno );
}

// refresh the mixer levels
static void
emixer_refresh()
{
  int      sid_count;
  snd_mixer_elem_t*   elem;

//  if ( !g_mixer_refresh )
//    return;

  // loop through the array, populate it with selem_ids
  sid_count = 0;

  for ( elem = snd_mixer_first_elem ( g_mixer_handle ); elem; elem = snd_mixer_elem_next ( elem ) )
  {
    if ( !snd_mixer_selem_is_active ( elem ) )
    {
      g_mixer_data[sid_count].elem = NULL;
      continue;
    }

    g_mixer_data[sid_count].elem = elem;
    snd_mixer_selem_get_id ( elem, g_mixer_data[sid_count].selem_id );
    sid_count++;
  }
}


// init the mixer stuff, grab info from alsa about the HW
int
emixer_mixer_init ( const char* card_id )
{
  snd_ctl_card_info_t* card_info;
  snd_ctl_t*    ctl_handle;
  int      err;

  // get card info
  snd_ctl_card_info_alloca ( &card_info );

  if ( ( err = snd_ctl_open ( &ctl_handle, card_id, 0 ) ) < 0 )
    emixer_abort ( ERR_OPEN, "snd_ctl_open", err );

  if ( ( err = snd_ctl_card_info ( ctl_handle, card_info ) ) < 0 )
    emixer_abort ( ERR_FCN, "snd_ctl_card_info", err );

  snd_ctl_close ( ctl_handle );

  // open mixer device
  if ( ( err = snd_mixer_open ( &g_mixer_handle, 0 ) ) < 0 )
    emixer_abort ( ERR_FCN, "snd_mixer_open", err );

  if ( ( err = snd_mixer_attach ( g_mixer_handle, card_id ) ) < 0 )
    emixer_abort ( ERR_FCN, "snd_mixer_attach", err );

  if ( ( err = snd_mixer_selem_register ( g_mixer_handle, NULL, NULL ) ) < 0 )
    emixer_abort ( ERR_FCN, "snd_mixer_selem_register", err );

  snd_mixer_set_callback ( g_mixer_handle, emixer_mixer_cb );

  if ( ( err = snd_mixer_load ( g_mixer_handle ) ) < 0 )
    emixer_abort ( ERR_FCN, "snd_mixer_load", err );

  // setup global card info variables
  strcpy ( g_card_name, snd_ctl_card_info_get_name ( card_info ) );
  strcpy ( g_device_name, snd_ctl_card_info_get_mixername ( card_info ) );

  // alloc an array of mixer data elems
  g_mixer_n_data = snd_mixer_get_count ( g_mixer_handle );
  g_mixer_data = ( mixer_data_t* ) malloc ( sizeof ( mixer_data_t ) * g_mixer_n_data );

  if ( !g_mixer_data )
    emixer_abort ( ERR_FCN, "malloc", 0 );

  // malloc the selems
  int i;
  for ( i = 0;i < g_mixer_n_data;++i )
    if ( snd_mixer_selem_id_malloc ( &(g_mixer_data[i].selem_id) ) < 0 )
      emixer_abort ( ERR_FCN, "snd_mixer_selem_id_maloc", 0 );

  // populate with data
  emixer_refresh();
}

// clean up all our foo
void
emixer_mixer_deinit()
{
  if ( g_mixer_data )
  {
    int i;
    for ( i = 0;i < g_mixer_n_data;++i )
      snd_mixer_selem_id_free ( g_mixer_data[i].selem_id );
  }

  free(g_mixer_data);
  g_mixer_data=NULL;
}

const char *
emixer_card_name_get()
{
  return g_card_name;
}

const char *
emixer_device_name_get()
{
  return g_device_name;
}

const int
emixer_elem_num_get()
{
  return g_mixer_n_data;
}

const char *
emixer_elem_name_get ( const int idx )
{
  return snd_mixer_selem_get_name ( g_mixer_data[idx].elem );
}

int
emixer_elem_is_active ( const int idx )
{
  return ( int ) g_mixer_data[idx].elem;
}

int
emixer_elem_vol_range_get ( const int idx, long * min, long * max )
{
  long vol_min, vol_max;

  if ( !g_mixer_data[idx].elem )
    return -1;

  if ( snd_mixer_selem_has_playback_volume ( g_mixer_data[idx].elem ) )
  {
    if ( snd_mixer_selem_get_playback_volume_range ( g_mixer_data[idx].elem,
                                                     min, max ) < 0 )
      return -2;
    return 0;
  }
  return -1;
}

int
emixer_elem_vol_now_get ( const int idx, const int chan, long* vol )
{
  long vol_now;

  if ( !g_mixer_data[idx].elem )
    return -1;

  if ( snd_mixer_selem_has_playback_volume ( g_mixer_data[idx].elem ) )
  {
    if ( snd_mixer_selem_get_playback_volume ( g_mixer_data[idx].elem,
                                               chan, vol ) < 0 )
      return -2;
    return 0;
  }
  return -1;
}

int
emixer_elem_vol_now_set(const int idx, const int chan, const long vol)
{
  if ( !g_mixer_data[idx].elem )
    return -1;

  if ( snd_mixer_selem_has_playback_volume ( g_mixer_data[idx].elem ) )
  {
    if(snd_mixer_selem_set_playback_volume(g_mixer_data[idx].elem,
                                           chan, vol) < 0 )
      return -2;
  }
 return -1;
}

int
emixer_elem_mute_get(const int idx, const int chanel)
{
  //if ( !g_mixer_data[idx].elem )
  //  return -1;

  if( snd_mixer_selem_has_playback_switch( g_mixer_data[idx].elem ) )
  {
    int sw;
    snd_mixer_selem_get_playback_switch(g_mixer_data[idx].elem, chanel, &sw);
    return sw;
  }
  return -1;
}

int
emixer_elem_mute_set(const int idx, const int chanel, int set)
{
  if ( !g_mixer_data[idx].elem )
    return -1;

  if( snd_mixer_selem_has_playback_switch( g_mixer_data[idx].elem ) )
  {
    return snd_mixer_selem_set_playback_switch ( g_mixer_data[idx].elem,
                                                  chanel, set);
  }
  return -1;
}



